<?php
namespace Lib;

/**
 * SOCKET 连接超时时间为 10 秒钟。
 * 本类的一个对象可多次完成一个发送者对一个接收者的邮件发送任务。
 * 无法完成一个发送者对多个接收者或多个发送者对一个接收者的邮件发送任务。
 * 提示：调试本类时，可监察 Smtp::$response 属的值。
 * 注意：如果邮件消息中的发件人地址和 MAIL FROM 命令所发送的地址不同，那么一些 SMTP 服务将拒绝发送，比如 qq.com、sohu.com 等。
 *
 * - 方法调用顺序：
 * - 1：__contruct()
 * - 与 SMTP 服务器建立连接
 * - 2：message()、append()
 * - 定义邮件
 * - 3：send()
 * - 发送邮件
 * - 4：quit()
 * - 如果不再发送邮件，则断开与 SMTP 服务器的连接
 *
 * - 示例：
 * - $attachment = array(array("name" => "ico.gif", "data" => "./favicon.gif", "cid" => "ResID.100", "type" => "image/gif", "url" => true, "mode" => true), array("data" => "./favicon.ico", "cid" => "ResID.101", "type" => "x-icon", "url" => true, "mode" => false));
 * - $resource = array(array("data" => fread(fopen("./noScript.jpg", "r"), filesize("./noScript.jpg")), "type" => "image/jpeg", "url" => false, "mode" => true));
 * - // 构造方法
 * - $smtp = new smtp("susisoy", "s12343141524", "susisoy@163.com", "smtp.163.com", "utf-8", true);
 * - $smtp->append(false, "", "build.xml", "", "text/xml", true, false);
 * - // 定义邮件消息中所出现的内容
 * - $smtp->message("测试", fread(fopen("index.html", "r"), filesize("index.html")), $attachment, $resource, true, true);
 * - // 发送定义好的消息
 * - $smtp->send(array("侯国全1<susisoy@163.com>", "侯国全2<susisoy@126.com>"), "SUSISOY<susisoy@163.com>", array("侯扬帆1<susisoy@163.com>", "侯扬帆2<susisoy@126.com>"), 5, 1);
 * - // 结束会话并关闭连接
 * - $smtp->quit();
 *
 * @author SUSISOY
 *
 */
class Smtp {
	public $subject; // 邮件标题
	public $response = array(); // 最后一条命令的所有响应信息，一行响应信息为一个数组元素
	

	private $fsock; // 与服务器的连接
	private $mailer; // 请求 SMTP 服务器发送邮件的地址，即 MAIL FROM 命令的参数值
	private $charset; // 邮件中字符所属字符集
	private $text; // 邮件正文
	private $header; // 邮件头信息
	private $body; // 邮件体
	

	public $attachment = array(); // 附件列表，一维数组
	public $resource = array(); // 内嵌资源

	
	/**
	 * 构造方法，也可在对象执行更换 SMTP 服务器动作时被调用
	 *
	 * @param string $user 用于 SMTP 认证的用户名
	 * @param string $pass 用于 SMTP 认证的密码
	 * @param string $mailer 请求发送邮件的地址，即 MAIL FROM 命令的参数值，注意，地址格式为：susisoy@163.com
	 * @param string $host SMTP 服务器
	 * @param string $port 连接 SMTP 服务器的端口
	 * @param string $timeout 连接 SMTP 服务器的超时时间（秒）
	 * @param string $charset 邮件中字符所属字符集
	 * @param string $ehlo 发件客户端主机名
	 * @return boolean
	 */
	public function __construct($user = null, $pass = null, $mailer = null, $host = 'localhost', $port = 25, $timeout = 10, $charset = 'UTF-8', $ehlo = '') {
		$this->mailer = "<$mailer>";
		
		$this->charset = $charset;
		
		if ($this->fsock = fsockopen($host, $port, $errno, $errstr, $timeout)) {
			if (!$this->getResp('220')) {
				return false;
			}
			
			if (!$this->command('EHLO ' . ($ehlo ?  : $_SERVER['SERVER_NAME']), '250')) {
				return false;
			}
			
			foreach ($this->response as $tmp) {
				// 需要身份认证
				if (preg_match('/AUTH.+LOGIN/', $tmp)) {
					if (!$this->command('AUTH LOGIN', '334')) {
						return false;
					}
					
					if (!$this->command(base64_encode($user), '334')) {
						return false;
					}
					
					if (!$this->command(base64_encode($pass), '235')) {
						return false;
					}
					
					break;
				}
			}
			
			return true;
		}
		
		return false;
	}

	/**
	 * 发送一个命令
	 *
	 * @param string $command 命令串
	 * @param string $respCode SMTP 服务器正确执行命令后的响应码
	 * @return boolean
	 */
	private function command($command, $respCode) {
		if (!fputs($this->fsock, "$command\r\n")) {
			return false;
		}
		
		if ($this->getResp($respCode)) {
			return true;
		}
		
		return false;
	}

	/**
	 * 发送过一条命令后，读取 SMTP 响应信息，并通过响应码判断命令是否被正确执行
	 *
	 * @param string $respCode SMTP 服务器正确执行命令后的响应码
	 * @return boolean
	 */
	private function getResp($respCode) {
		$this->response = array();
		
		while ($tmp = fgets($this->fsock, 512)) {
			$this->response[] = $tmp;
			
			if (substr($tmp, 3, 1) == " ") {
				break;
			}
		}
		
		if (substr($this->response[0], 0, 3) == $respCode) {
			return true;
		}
		
		return false;
	}

	/**
	 * 添加一个附件或一个邮件资源，该方法可手工调用添加一个附件或资源，也可由对象自身执行 message() 或 parse() 时被联动执行
	 *
	 * @param boolean $inline 是否为一个邮件资源，默认为一个资源(true)
	 * @param string $name 资源或附件文件的名称，默认资源名称为“ResID.***”而附件名称为“附件***.dat”，但 data 参数值为资源或附件的 URL 时，默认资源名或附件的名称有可能来源于 URL 中
	 * @param string $data 资源或附件的数据，也可能是资源或附件的 URL(参见 url 参数)
	 * @param string $cid 如果为资源，则此参数指定了资源的 ID，默认的资源 ID 为资源的名称，参见 name 参数
	 * @param string $type 资源或附件的数据类型，例如：image/gif、image/jpeg
	 * @param boolean $url 如果 data 的值为资源或附件的 URL，而并非资源或附件的数据，则此参数值为 true
	 * @param boolean $mode 如果当前资源同时要出现在附件列表中或当前附件同时要出现在资源列表中，则此参数值应为 true
	 * @return boolean
	 */
	public function append($inline = true, $name = null, $data = null, $cid = null, $type = null, $url = false, $mode = true) {
		if ($url) {
			if (!($data = $this->getRes($data))) {
				return false;
			} elseif (!$name) {
				if (preg_match("#[^/\\\\]+\.\w+$#", $url, $tmp)) {
					$name = $tmp[0];
				}
			}
		}
		
		if ($inline == true) {
			$key = count($this->resource);
			
			if (!$name) {
				$name = "ResID.{$key}";
			}
			
			$this->resource[$key]["name"] = $name;
			$this->resource[$key]["cid"] = $cid ? $cid : $name;
			$this->resource[$key]["type"] = $type ? $type : "application/octet-stream";
			$this->resource[$key]["data"] = $data;
			$this->resource[$key]["attachment"] = $mode;
		} else {
			$key = count($this->attachment);
			
			if (!$name) {
				$name = "附件{$key}.dat";
			}
			
			$this->attachment[$key]["name"] = $name;
			$this->attachment[$key]["cid"] = $cid ? $cid : $name;
			$this->attachment[$key]["type"] = $type ? $type : "application/octet-stream";
			$this->attachment[$key]["data"] = $data;
			$this->attachment[$key]["inline"] = $mode;
		}
		
		return true;
	}

	/**
	 * 自动完成邮件正文中所引用的资源添加至邮件资源列表中
	 *
	 * @return boolean
	 */
	private function parse() {
		/**
		 * 处理 src 资源
		 *
		 * 以下正则式通常用于匹配：
		 * <img...... src="......"......>
		 *
		 * 以下字符串并不会匹配：
		 * <img...... src="cid:......"......>
		 */
		if (preg_match_all("/<(\w+)[^>]+src\s*=(?!['\"\s]*cid:)['\"\s]*([^'\"\s>]+)[^>]*>/i", $this->text, $tmp)) {
			$resList = $tmp[1];
			$urlList = array_unique($tmp[2]);
			
			foreach ($urlList as $resID => $url) {
				$type = substr($url, strrpos($url, ".") + 1);
				
				switch (strtolower($type)) {
					case "jpg" :
					case "jpeg" :
						$type = "image/jpeg";
						break;
					case "gif" :
						$type = "image/gif";
						break;
					case "png" :
						$type = "image/x-png";
						break;
					case "swf" :
						$type = "application/x-shockwave-flash";
						break;
					case "wav" :
						$type = "audio/wav";
						break;
					case "mp3" :
						$type = "audio/mpeg";
						break;
					case "mid" :
						$type = "audio/mid";
						break;
					default :
						switch (strtolower($resList[$resID])) {
							case "img" :
								$type = "image/gif";
								break;
							case "embed" :
								$type = "application/x-shockwave-flash";
								break;
							default :
								$type = "";
								break;
						}
						
						break;
				}
				
				if ($type == '') {
					continue;
				}
				
				$key = count($this->resource);
				
				if ($data = $this->getRes($url)) {
					$cid = "ResID.{$key}";
					
					preg_match("#[^/\\\\]+$#", $url, $tmp);
					
					$this->resource[$key]["cid"] = $cid;
					$this->resource[$key]["name"] = isset($tmp[0]) ? $tmp[0] : $cid;
					$this->resource[$key]["type"] = $type;
					$this->resource[$key]["data"] = $data;
					
					$this->text = preg_replace("/(<\w+[^>]+)src\s*=(?!['\"\s]*cid:)['\"\s]*" . preg_quote($url, "/") . "['\"]*(\s*[^>]*>)/i", "\\1src=\"cid:$cid\"\\2", $this->text);
				}
			}
		}
		
		/**
		 * 处理 link 资源
		 *
		 * 以下正则式匹配：
		 * <link...... href="......"......>
		 *
		 * 以下字符串并不会匹配：
		 * <link...... href="cid:......"......>
		 */
		if (preg_match_all("/<link[^>]+href\s*=(?!['\"\s]*cid:)['\"\s]*([^'\"\s>]+)[^>]*>/i", $this->text, $linkRes)) {
			$linkRes[1] = array_unique($linkRes[1]);
			
			foreach ($linkRes[1] as $resID => $url) {
				if (preg_match("/\Wrel\s*=['\"\s]+([^'\"]+)['\"]+\W/i", $linkRes[0][$resID], $rel)) {
					switch (strtolower($rel[1])) {
						case "stylesheet" :
							if ($data = $this->getRes($url)) {
								$this->text = preg_replace("/<link[^>]+href\s*=(?!['\"\s]*cid:)['\"\s]*" . preg_quote($url, "/") . "[^>]*>/i", "<style>\r\n$data\r\n</style>\r\n", $this->text);
							}
							
							break;
						default :
							break;
					}
				}
			}
		}
		
		/**
		 * 处理导入的 CSS 资源
		 *
		 * 以下正则式匹配：
		 * @import url("......");
		 * @import "......"
		 *
		 * 以下字符串并不会匹配：
		 * @import url("cid:......");
		 * @import "cid:......"
		 */
		while (preg_match_all("/@import\s+(?:url)?(?!['\"\s\(]+cid:)['\"\s\(]+([^'\"\s\)]+)[^;\r\n]+[;\r\n]/i", $this->text, $tmp)) {
			$urlList = array_unique($tmp[1]);
			$amount = count($urlList);
			
			foreach ($urlList as $url) {
				if ($data = $this->getRes($url)) {
					$this->text = preg_replace("/@import\s+(?:url)?(?!['\"\s\(]+cid:)['\"\s\(]+" . preg_quote($url, "/") . "[^;\r\n]+[;\r\n]/i", "$data\r\n", $this->text);
				} else {
					$amount--;
				}
			}
			
			if ($amount == 0) {
				break;
			}
		}
		
		/**
		 * 处理通过 HTML 元素的 background 属性指定的背景图片资源
		 *
		 * 以下正则式通常用于匹配：
		 * <td...... background="......"......>
		 *
		 *
		 * 以下字符串并不会匹配：
		 * <td...... background="cid:......"......>
		 */
		if (preg_match_all("/<[^>]+background\s*=(?!['\"\s]*cid:)['\"\s]*([^'\"\s>]+)[^>]*>/i", $this->text, $tmp)) {
			$urlList = array_unique($tmp[1]);
			
			foreach ($urlList as $resID => $url) {
				$key = count($this->resource);
				
				if ($data = $this->getRes($url)) {
					$cid = "ResID.{$key}";
					
					preg_match("#[^/\\\\]+$#", $url, $tmp);
					
					$this->resource[$key]["cid"] = $cid;
					$this->resource[$key]["name"] = isset($tmp[0]) ? $tmp[0] : $cid;
					$this->resource[$key]["data"] = $data;
					$type = substr($url, strrpos($url, ".") + 1);
					
					switch (strtolower($type)) {
						case "gif" :
							$this->resource[$key]["type"] = "image/gif";
							break;
						case "png" :
							$this->resource[$key]["type"] = "image/x-png";
							break;
						default :
							$this->resource[$key]["type"] = "image/jpeg";
							break;
					}
					
					$this->text = preg_replace("/(<\w+[^>]+)background\s*=(?!['\"\s]*cid:)['\"\s]*" . preg_quote($url, "/") . "['\"]*(\s*[^>]*>)/i", "\\1background=\"cid:$cid\"\\2", $this->text);
				}
			}
		}
		
		/**
		 * 处理通过 CSS 的 background-image 样式指定的背景图片资源
		 *
		 * 以下正则式通常用于匹配：
		 * 注意：url 与 "(" 字符之前不能有任何字符；
		 * 提示：括引图片地址的一对引号可以省略；
		 * background-image:url("......");
		 *
		 * 以下字符串并不会匹配：
		 * background-image: url("cid:......");
		 */
		if (preg_match_all("/background-image\s*:\s*url(?!['\"\s\(]+cid:)\(([^\)]+)\)[^;\r\n]*[;\r\n]/i", $this->text, $tmp)) {
			$urlList = array_unique($tmp[1]);
			
			foreach ($urlList as $url) {
				$url = trim($url, "'\" ");
				$key = count($this->resource);
				
				if ($data = $this->getRes($url)) {
					$cid = "ResID.{$key}";
					
					preg_match("#[^/\\\\]+$#", $url, $tmp);
					
					$this->resource[$key]["cid"] = $cid;
					$this->resource[$key]["name"] = isset($tmp[0]) ? $tmp[0] : $cid;
					$this->resource[$key]["data"] = $data;
					$type = substr($url, strrpos($url, ".") + 1);
					
					switch (strtolower($type)) {
						case "gif" :
							$this->resource[$key]["type"] = "image/gif";
							break;
						case "png" :
							$this->resource[$key]["type"] = "image/x-png";
							break;
						default :
							$this->resource[$key]["type"] = "image/jpeg";
							break;
					}
					
					$this->text = preg_replace("/(background-image\s*:\s*url(?!['\"\s\(]+cid:)['\"\s\(]+)" . preg_quote($url, "/") . "(['\"\s\)]+[^;\r\n]*[;\r\n])/i", "\\1cid:{$cid}\\2", $this->text);
				}
			}
		}
		
		return true;
	}

	/**
	 * $attachment 参数格式为(参见 append() 方法中的参数)：array("name"=>"","data"=>"","cid"=>"","type"=>"","url"=>"","mode"=>"");
	 * $resource 参数格式为(参见 append() 方法中的参数)：array("name"=>"","data"=>"","cid"=>"","type"=>"","url"=>"","mode"=>"");
	 *
	 * @param string $subject 邮件标题
	 * @param string $text 邮件正文
	 * @param array $attachment 向邮件中添加的附件列表
	 * @param array $resource 向邮件中添加的资源列表
	 * @param boolean $html 邮件正文是否为“超文本”格式
	 * @param boolean $parse 是否对邮件正文中所引用的资源进行解析，即将邮件正文中所引用的资源添加至邮件资源中
	 * @return boolean
	 */
	public function message($subject, $text, $attachment = null, $resource = null, $html = true, $parse = false) {
		$this->subject = $subject;
		
		$this->text = $text;
		
		if ($attachment) {
			foreach ($attachment as $tmp) {
				$this->append(false, $tmp["name"], $tmp["data"], $tmp["cid"], $tmp["type"], $tmp["url"], $tmp["mode"]);
			}
		}
		
		if ($resource) {
			foreach ($resource as $tmp) {
				$this->append(true, $tmp["name"], $tmp["data"], $tmp["cid"], $tmp["type"], $tmp["url"], $tmp["mode"]);
			}
		}
		
		$parse && $this->parse();
		
		$this->body($html);
		
		return true;
	}

	/**
	 * 由 send() 方法调用，组结并构成邮件的头信息部分，正确执行后，$this->header 的值即邮件的头信息内容
	 *
	 * @param string $to 邮件接收者，参见 send() 方法
	 * @param string $from 邮件发送者，参见 send() 方法，默认为空，即匿名邮件，提示：该内容仅用于显示邮件发送者
	 * @param array||string $reply 回复邮件至，参见 send() 方法，默认为 from 参数的值
	 * @param int $priority 邮件的优先级，默认为3，即正常
	 * @param boolean $receipt 邮件是否要求“收到报告”，默认为不需要
	 * @return boolean
	 */
	private function header($to, $from = null, $reply = null, $priority = 3, $receipt = false) {
		// Message-ID:
		$this->header = "Message-ID: <MSGID.susisoy.php.class.smtp." . strtoupper(uniqid("")) . "@" . ($_SERVER["REMOTE_ADDR"] ? $_SERVER["REMOTE_ADDR"] : "127.0.0.1") . "-" . gethostbyname($_SERVER["SERVER_NAME"]) . ">\r\n";
		
		// Subject:
		$this->header .= "Subject: =?{$this->charset}?B?" . base64_encode($this->subject) . "?=\r\n";
		
		// To:
		$this->header .= "To: " . $this->encode($to) . "\r\n";
		
		// From:
		if ($from) {
			$this->header .= "From: " . $this->encode($from) . "\r\n";
		}
		
		// Reply-To:
		if ($reply) {
			$this->header .= "Reply-To: " . $this->encode($reply) . "\r\n";
		} elseif ($from) {
			$this->header .= "Reply-To: " . $this->encode($from) . "\r\n";
		}
		
		// Disposition-Notification-To:
		if ($receipt && $from) {
			$this->header .= "Disposition-Notification-To: " . $from . "\r\n";
		}
		
		// Date:
		$this->header .= "Date: " . date("r") . "\r\n";
		
		// X-Priority:
		$this->header .= "X-Priority: $priority\r\n";
		
		// X-MSMail-Priority:
		if ($priority == 3) {
			$this->header .= "X-MSMail-Priority: Normal\r\n";
		} elseif ($priority == 1) {
			$this->header .= "X-MSMail-Priority: High\r\n";
		} else {
			$this->header .= "X-MSMail-Priority: Low\r\n";
		}
		
		// X-Mailer:
		$this->header .= "X-Mailer: susisoy.php.class.smtp\r\n";
		
		// MIME-Version:1.0
		$this->header .= "MIME-Version: 1.0\r\n";
		
		return true;
	}

	/**
	 * 由 send() 方法调用，组结并构成邮件的体部分，正确执行后，$this->body 的值即邮件的体内容
	 *
	 * @param boolean $html 邮件正文是否为“超文本”格式
	 * @return boolean
	 */
	private function body($html = true) {
		$this->body = "";
		
		if ($this->attachment) { // 如果有附件,邮件体类型为 multipart/mixed
			$this->body .= "Content-Type: multipart/mixed;\r\n\tboundary=\"HHO.LIB.SMTP.BOUNDARY.MIXED\"\r\n\r\n";
			$this->body .= "--HHO.LIB.SMTP.BOUNDARY.MIXED\r\n";
		}
		
		if ($this->resource) { // 如果有资源,邮件体类型为 multipart/related
			$this->body .= "Content-Type: multipart/related;\r\n\tboundary=\"HHO.LIB.SMTP.BOUNDARY.RELATED\"\r\n\r\n";
			$this->body .= "--HHO.LIB.SMTP.BOUNDARY.RELATED\r\n";
		}
		
		// 用于显示的邮件正文
		$this->body .= "Content-Type: multipart/alternative;\r\n\tboundary=\"HHO.LIB.SMTP.BOUNDARY.ALTERNATIVE\"\r\n\r\n";
		$this->body .= "--HHO.LIB.SMTP.BOUNDARY.ALTERNATIVE\r\n";
		
		if ($html) { // 邮件正文为超文本
			$this->body .= "Content-Type: text/plain; charset=\"{$this->charset}\"\r\n";
			$this->body .= "Content-Transfer-Encoding: base64\r\n\r\n";
			$this->body .= chunk_split(base64_encode($this->text)) . "\r\n";
			$this->body .= "--HHO.LIB.SMTP.BOUNDARY.ALTERNATIVE\r\n";
			$this->body .= "Content-Type: text/html; charset=\"{$this->charset}\"\r\n";
			$this->body .= "Content-Transfer-Encoding: base64\r\n\r\n";
			$this->body .= chunk_split(base64_encode($this->text)) . "\r\n";
		} else { // 邮件正文为纯文本
			$this->body .= "Content-Type: text/plain\; charset=\"{$this->charset}\"\r\n";
			$this->body .= "Content-Transfer-Encoding: base64\r\n\r\n";
			$this->body .= chunk_split(base64_encode($this->text)) . "\r\n";
		}
		
		$this->body .= "--HHO.LIB.SMTP.BOUNDARY.ALTERNATIVE--\r\n\r\n";
		
		if ($this->resource) { // 如果有资源,在此定义各资源段
			foreach ($this->resource as $tmp) {
				$this->body .= "--HHO.LIB.SMTP.BOUNDARY.RELATED\r\n";
				$this->body .= "Content-Type: {$tmp["type"]}; name=\"=?{$this->charset}?B?" . base64_encode($tmp["name"]) . "?=\"\r\n";
				$this->body .= "Content-ID: <{$tmp["cid"]}>\r\n";
				
				if (isset($tmp["attachment"]) && $tmp["attachment"]) {
					$this->body .= "Content-Disposition: attachment; filename=\"=?{$this->charset}?B?" . base64_encode($tmp["name"]) . "?=\"\r\n";
				}
				
				$this->body .= "Content-Transfer-Encoding: base64\r\n\r\n";
				$this->body .= chunk_split(base64_encode($tmp["data"])) . "\r\n";
			}
			
			$this->body .= "--HHO.LIB.SMTP.BOUNDARY.RELATED--\r\n\r\n";
		}
		
		if ($this->attachment) { // 如果有附件，在此定义各附件段
			foreach ($this->attachment as $tmp) {
				$this->body .= "--HHO.LIB.SMTP.BOUNDARY.MIXED\r\n";
				$this->body .= "Content-Type: {$tmp["type"]}; name=\"=?{$this->charset}?B?" . base64_encode($tmp["name"]) . "?=\"\r\n";
				$this->body .= "Content-Disposition: attachment; filename=\"=?{$this->charset}?B?" . base64_encode($tmp["name"]) . "?=\"\r\n";
				
				if ($tmp["inline"]) {
					$this->body .= "Content-ID: <{$tmp["cid"]}>\r\n";
				}
				
				$this->body .= "Content-Transfer-Encoding: base64\r\n\r\n";
				$this->body .= chunk_split(base64_encode($tmp["data"])) . "\r\n";
			}
			
			$this->body .= "--HHO.LIB.SMTP.BOUNDARY.MIXED--\r\n";
		}
		
		return true;
	}

	/**
	 * 发送已定义好的邮件，注意：所定义的邮件头信息部分将在此方法被调用后才实际生成
	 *
	 * $to、$from、$reply 的合法的格式有：“<susisoy@163.com>”、“susisoy<susisoy@163.com>”、“susisoy <susisoy@163.com>”、“"susisoy"<susisoy@163.com>”、“"susisoy" <susisoy@163.com>”
	 *
	 * @param array|string $to 邮件接收者列表
	 * @param string $from 邮件发送者，默认没有发送者，即匿名邮件
	 * @param array|string $reply 邮件回复至列表
	 * @param int $priority 邮件的优先级别
	 * @param boolean $receipt 是否要求“收到报告”
	 * @return boolean
	 */
	public function send($to, $from = null, $reply = null, $priority = 3, $receipt = false) {
		if (!is_array($to)) {
			$to = array($to);
		}
		
		foreach ($to as $curTo) {
			$this->header($curTo, $from, $reply, $priority, $receipt);
			
			if (!$this->command("MAIL FROM:{$this->mailer}", "250")) {
				return false;
			}
			
			if (is_array($curTo)) {
				foreach ($curTo as $item) {
					$item = preg_replace("/.*(<[\w\-\.]+@[\w\-\.]+>)/", "\\1", $item);
					
					if (!$this->command("RCPT TO:{$item}", "250")) {
						return false;
					}
				}
			} else {
				$curTo = preg_replace("/.*(<[\w\-\.]+@[\w\-\.]+>)/", "\\1", $curTo);
				
				if (!$this->command("RCPT TO:{$curTo}", "250")) {
					return false;
				}
			}
			
			if (!$this->command("DATA", "354")) {
				return false;
			}
			
			if (!$this->command("{$this->header}{$this->body}.", "250")) {
				return false;
			}
			
			/**
			 * 经测试，执行 RSET 命令后，某些 SMTP 服务器返回 250，而有些则返回 220
			 */
			if (!$this->command("RSET", "250")) {
				return false;
			}
		}
		
		return true;
	}

	/**
	 * 由 header() 方法联动调用
	 *
	 * @param string $string 要进行 base64 编码的字符串
	 * @return string
	 */
	private function encode($string) {
		if (is_array($string)) {
			$join = array();
			
			foreach ($string as $tmp) {
				$join[] = $this->encode($tmp);
			}
			
			return implode(', ', $join);
		}
		
		if (preg_match('/"?([^"]*)"?(<.*>)/', $string, $tmp)) {
			return "\"=?{$this->charset}?B?" . base64_encode($tmp[1]) . "?=\"" . $tmp[2];
		} else {
			return "=?{$this->charset}?B?" . base64_encode($string) . "?=";
		}
	}

	/**
	 * 读取一个指定 URL 的资源或附件的数据
	 *
	 * @param string $url 资源或附件的 URL
	 * @return string
	 */
	private function getRes($url) {
		if ($fp = fopen($url, "rb")) {
			$data = null;
			
			while ($tmp = fread($fp, 1024)) {
				$data .= $tmp;
			}
			
			return $data;
		}
		
		return false;
	}

	/**
	 * 结束邮件发送并断开与 SMTP 服务器的连接
	 *
	 * @return boolean
	 */
	public function quit() {
		if (!$this->command("RSET", "250")) {
			return false;
		}
		
		if (!$this->command("QUIT", "221")) {
			return false;
		}
		
		if (!fclose($this->fsock)) {
			return false;
		}
		
		return true;
	}
}